package dev.heaven.rabbit.config;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;

import java.net.InetSocketAddress;

@Configuration
@Slf4j
public class NettyServerRunner implements CommandLineRunner, ApplicationContextAware {

    private static final String WS_PATH = "/ws";

    private ApplicationContext applicationContext;

    @Override
    public void run(String... args) {
        InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8090);
        //创建主线程组，接收请求
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //创建从线程组，处理主线程组分配下来的io操作
        int threads = Runtime.getRuntime().availableProcessors() * 2;
        EventLoopGroup workGroup = new NioEventLoopGroup(threads);
        ServerBootstrap bootstrap = new ServerBootstrap()
                //设置主从线程组
                .group(bossGroup, workGroup)
                //设置通道
                .channel(NioServerSocketChannel.class)
                //子处理器，用于处理workerGroup中的操作
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        //http解码器（websocket是基于http协议的，所以可以直接用现成的http解码器）
                        pipeline.addLast(new HttpServerCodec());
                        //对写大数据流的支持
                        pipeline.addLast(new ChunkedWriteHandler());
                        //对HttpMessage进行聚合
                        //几乎在netty编程中，都会使用到handler
                        pipeline.addLast(new HttpObjectAggregator(1024 *64));
                        //设置单次请求的文件的大小
                        pipeline.addLast(new HttpObjectAggregator(65535));
                        pipeline.addLast(new IdleStateHandler(4,8,12));
                        // WebSocket数据压缩
                        pipeline.addLast(new WebSocketServerCompressionHandler());
                        //websocket 服务器处理的协议，用于指定给客户端连接访问的路由 :/ws
                        pipeline.addLast(new WebSocketServerProtocolHandler(WS_PATH, null, true));
                        pipeline.addLast(applicationContext.getBeansOfType(ChannelInboundHandler.class).values().toArray(new ChannelHandler[0]));
                    }
                }).localAddress(socketAddress)
                //服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
                .option(ChannelOption.SO_BACKLOG, 1024)
                //将小的数据包包装成更大的帧进行传送，提高网络的负载,即TCP延迟传输
                .childOption(ChannelOption.TCP_NODELAY, true)
                // 两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
                .childOption(ChannelOption.SO_KEEPALIVE, true);
        //绑定端口,开始接收进来的连接
        try {
            ChannelFuture future = bootstrap.bind(socketAddress).sync();
            log.info("netty server listening port: {}", socketAddress.getPort());
            //监听关闭channel
            future.channel().closeFuture().sync();
        } catch (InterruptedException ignored) {

        } finally {
            //关闭主线程组
            bossGroup.shutdownGracefully();
            //关闭工作线程组
            workGroup.shutdownGracefully();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
